package com.xuebusi.nio.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO 服务器端
 */
public class NioServer {

    // Server端口
    public static final int TCP_SERVER_PORT = 8000;

    public static void main(String[] args) throws IOException {
        new NioServer().start();
    }

    /**
     * 启动 NIO 服务器端
     *
     * @throws IOException
     */
    public void start() throws IOException {
        /**
         * 1.创建Selector
         */
        Selector selector = Selector.open();

        /**
         * 2.通过ServerSocketChannel创建channel通道
         */
        ServerSocketChannel ssc = ServerSocketChannel.open();

        /**
         * 3.为channel通道绑定监听端口
         */
        ssc.bind(new InetSocketAddress(TCP_SERVER_PORT));

        /**
         * 4.设置channel为非阻塞模式
         */
        ssc.configureBlocking(false);

        /**
         * 5.将channel注册到Selector上，监听连接事件
         */
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动成功");

        /**
         * 6.循环等待新接入的连接
         */
        for (; ; ) {
            // 获取可用的channel数量
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;
            // 获取所有就绪(可用)的channel集合
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
                SelectionKey selectionKey = it.next();
                // 移除集合中当前key
                it.remove();

                /**
                 * 7.根据就绪状态，调用对应的方法处理业务逻辑
                 */
                // 如果是接入事件
                if (selectionKey.isAcceptable()) {
                    accepthandler(ssc, selector);
                }

                // 如果是可读事件
                if (selectionKey.isReadable()) {
                    readHandler(selectionKey, selector);
                }
            }
        }
    }

    /**
     * 接入事件处理器
     *
     * @param ssc
     * @param selector
     * @throws IOException
     */
    private void accepthandler(ServerSocketChannel ssc, Selector selector) throws IOException {
        // 如果是接入事件，创建socketChannel
        SocketChannel sc = ssc.accept();

        // 将SocketChannel设置为非阻塞模式
        sc.configureBlocking(false);

        // 将channel注册到selector上,监听可读事件
        sc.register(selector, SelectionKey.OP_READ);

        // 回复客户端提示信息
        sc.write(Charset.forName("UTF-8").encode("你与聊天室其他人都不是朋友关系,请注意隐私安全"));
    }

    /**
     * 可读事件处理器
     *
     * @param selectionKey
     * @param selector
     * @throws IOException
     */
    private void readHandler(SelectionKey selectionKey, Selector selector) throws IOException {
        // 从selectionKey中获取到已经就绪的channel
        SocketChannel sc = (SocketChannel) selectionKey.channel();

        // 创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        // 循环读取客户端请求信息
        String request = "";
        while (sc.read(byteBuffer) > 0) {
            // 切换为读模式
            byteBuffer.flip();
            // 读取buffer中的内容
            request += Charset.forName("UTF-8").decode(byteBuffer);
        }
        // 将channel再次注册到selector上,监听他的可读事件
        sc.register(selector, SelectionKey.OP_READ);

        // 将客户端发送的请求信息,广播给其他客户端
        if (request.length() > 0) {
            broadCast(selector, sc, request);
        }
    }

    /**
     * 广播消息给其他客户端
     *
     * @param selector
     * @param sourceChannel
     * @param request
     */
    private void broadCast(Selector selector, SocketChannel sourceChannel, String request) {
        //获取所有注册到Selector上的所有Channel的集合
        Set<SelectionKey> selectionKeySet = selector.keys();
        // 循环向所有的channel广播信息
        selectionKeySet.forEach(selectionKey -> {
            Channel targetChannel = selectionKey.channel();
            // 剔除发送消息的客户端
            if (targetChannel instanceof SocketChannel && targetChannel != sourceChannel) {
                try {
                    // 将消息发送到targetChannel客户端
                    ((SocketChannel) targetChannel).write(Charset.forName("UTF-8").encode(request));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
